package org.liang.common.utils.date;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;


/**
 * 日期基础工具类.
 * 
 * @author L.Yang
 * @version 1.0, 2014-7-9
 */
public final class DateBase {

    /** 系统默认 日期类型 yyyy-MM-dd. */
    public static final String DATE_PATTERN_DEFAULT = "yyyy-MM-dd";

    /** 时间 日期类型 HH:mm:ss. */
    public static final String DATE_PATTERN_TIME = "HH:mm:ss";

    /** 日期时间 日期类型 yyyy-MM-dd HH:mm:ss. */
    public static final String DATE_PATTERN_DATETIME = "yyyy-MM-dd HH:mm:ss";

    /** 一天毫秒数. */
    public static final long DAY_IN_MILLISECOND = DateUtils.MILLIS_PER_DAY;

    /** 一小时毫秒数. */
    public static final long HOUR_IN_MILLISECOND = DateUtils.MILLIS_PER_HOUR;

    /** 构造方法私有化 . */
    private DateBase() {}

    /**
     * 根据传入的日期格式 将字符串解析成 日期类型.
     * 
     * @param strDate
     *            日期字符串
     * @param pattern
     *            日期格式 如果传入格式为空，则为默认格式 yyyy-MM-dd
     * @return 日期类型
     */
    public static Date parseDate(String strDate, String pattern) {
        Date date = null;
        try {
            pattern = StringUtils.isEmpty(pattern) ? DATE_PATTERN_DEFAULT : pattern;
            date = DateUtils.parseDate(strDate, pattern);
        } catch (Exception e) {
            return date;
        }
        return date;
    }

    /**
     * 根据指定的转化模式，转换日期成字符串.
     * 
     * @param date
     *            需要转换的日期
     * @param pattern
     *            日期格式 如果传入格式为空，则为默认格式 yyyy-MM-dd
     * @return 转换后的字符串
     */
    public static String formatDate(Date date, String pattern) {
        pattern = StringUtils.isEmpty(pattern) ? DATE_PATTERN_DEFAULT : pattern;
        return date == null ? null : DateFormatUtils.format(date, pattern);
    }

    /**
     * 根据指定的转化模式，转换日期成字符串.
     * 
     * @param date
     *            需要转换的日期
     * @param pattern
     *            日期格式 如果传入格式为空，则为默认格式 yyyy-MM-dd
     * @return 转换后的字符串
     */
    public static String formatDate(Calendar cal, String pattern) {
        pattern = StringUtils.isEmpty(pattern) ? DATE_PATTERN_DEFAULT : pattern;
        return cal == null ? null : DateFormatUtils.format(cal, pattern);
    }


    /**
     * Adds to a date returning a new object. The original {@code Date} is unchanged.
     *
     * @param date
     *            the date, not null
     * @param calendarField
     *            the calendar field to add to
     * @param amount
     *            the amount to add, may be negative
     * @return the new {@code Date} with the amount added
     * @throws IllegalArgumentException
     *             if the date is null
     */
    public static Date add(final Date date, final int calendarField, final int amount) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        final Calendar c = getCalendar(date);
        c.add(calendarField, amount);
        return c.getTime();
    }

    /**
     * 在指定的日期基础上，增加或是减少天数.
     * 
     * @param date
     *            指定的日期
     * @param days
     *            需要增加或是减少的天数，正数为增加，负数为减少
     * @return 增加或是减少后的日期
     */
    public static Date addDays(Date date, int days) {
        long now = date.getTime() + days * DAY_IN_MILLISECOND;
        return (new Date(now));
    }

    /**
     * 在指定的日期基础上，增加或是减少天数.
     * 
     * @param date
     *            指定的日期
     * @param weeks
     *            需要增加或是减少的天数，正数为增加，负数为减少
     * @return 增加或是减少后的日期
     */
    public static Date addWeeks(Date date, int weeks) {
        return DateUtils.addWeeks(date, weeks);
    }

    /**
     * 在指定日期的基础上，增加或是减少月份信息，如1月31日，增加一个月后，则为2月28日（非闰年）.
     * 
     * @param date
     *            指定的日期
     * @param months
     *            增加或是减少的月份数，正数为增加，负数为减少
     * @return 增加或是减少后的日期
     */
    public static Date addMonths(Date date, int months) {
        return DateUtils.addMonths(date, months);
    }

    /**
     * 在指定的日期基础上，增加或是减少年数.
     * 
     * @param date
     *            指定的日期
     * @param years
     *            需要增加或是减少的年数，正数为增加，负数为减少
     * @return 增加或是减少后的日期
     */
    public static Date addYears(Date date, int years) {
        return DateUtils.addYears(date, years);
    }

    /**
     * 获取系统当前时间.
     * 
     * @return 系统当前时间
     */
    public static Date getCurrentDate() {
        return GregorianCalendar.getInstance().getTime();
    }

    /**
     * 得到日期的起始日期，例如2004-1-1 15:12，转换后为 2004-1-1 00:00 .
     * 
     * @param date
     *            需要转换的日期
     * @return 该日期的零点
     */
    public static Date getDayBegin(Date date) {
        Calendar cal = getCalendar(date);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 得到日期的结束日期，例如2004-1-1 15:12，转换后为2004-1-2 00:00，注意为第二天的0点整.
     * 
     * @param date
     *            所要转换的日期
     * @return 为第二天的零点整
     */
    public static Date getDayEnd(Date date) {
        Calendar cal = GregorianCalendar.getInstance();
        cal.setTime(addDays(date, 1));
        return getDayBegin(cal.getTime());
    }

    /**
     * 得到日期所在月份的开始日期（第一天的开始日期），例如2004-1-15 15:10，转换后为2004-1-1 00:00.
     * 
     * @param date
     *            需要转换的日期
     * @return 日期所在月份的开始日期
     */
    public static Date getMonthBegin(Date date) {
        Calendar cal = getCalendar(date);
        int year = cal.get(Calendar.YEAR);
        int mon = cal.get(Calendar.MONTH);
        return new GregorianCalendar(year, mon, 1).getTime();
    }

    /**
     * 根据年、月返回由年、月构成的日期的月份开始日期.
     * 
     * @param year
     *            所在的年
     * @param month
     *            所在的月份，从1月到12月
     * @return 由年、月构成的日期的月份开始日期
     */
    public static Date getMonthBegin(int year, int month) {
        if (month <= 0 || month > 12) {
            throw new IllegalArgumentException("month must from 1 to 12");
        }
        Calendar gc = new GregorianCalendar(year, month - 1, 1);
        return gc.getTime();
    }

    /**
     * 根据日期所在的月份，得到下个月的第一天零点整.
     * 
     * @param date
     *            需要转换的日期
     * @return 对应日期的下个月的第一天零点整
     */
    public static Date getMonthEnd(Date date) {
        Calendar cal = getCalendar(date);
        int lastDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
        cal.set(Calendar.DAY_OF_MONTH, lastDay);
        return getDayEnd(cal.getTime());
    }

    /**
     * 根据年、月返回由年、月构成的日期的下一个月第一天零点整.
     * 
     * @param year
     *            所在的年
     * @param month
     *            所在的月份，从1月到12月
     * @return 下一个月第一天零点整
     */
    public static Date getMonthEnd(int year, int month) {
        Date start = getMonthBegin(year, month);
        return getMonthEnd(start);
    }

    /**
     * 根据日期所在的星期，得到这个星期的开始日期，注意，每周从星期日开始计算.
     * 
     * @param date
     *            需要转换的日期
     * @return 传入日期所在周的第一天的零点整
     */
    public static Date getWeekBegin(Date date) {
        Calendar cal = getCalendar(date);
        int startDay = cal.getActualMinimum(Calendar.DAY_OF_WEEK);
        cal.set(Calendar.DAY_OF_WEEK, startDay);
        return cal.getTime();
    }

    /**
     * 根据日期所在的星期，得到下周开始第一天的零点整.
     * 
     * @param date
     *            需要转换的日期
     * @return 传入日期的下周开始第一天的零点整
     */
    public static Date getWeekEnd(Date date) {
        Calendar cal = getCalendar(date);
        int lastDay = cal.getActualMaximum(Calendar.DAY_OF_WEEK);
        cal.set(Calendar.DAY_OF_WEEK, lastDay);
        return getDayEnd(cal.getTime());
    }

    /**
     * 根据日期所在的年份，得到当年的开始日期，为每年的1月1日零点整.
     * 
     * @param date
     *            需要转换的日期
     * @return 当年的开始日期，为每年的1月1日零点整
     */
    public static Date getYearBegin(Date date) {
        Calendar cal = GregorianCalendar.getInstance();
        cal.setTime(date);
        cal.set(Calendar.DAY_OF_YEAR, 1);
        return getDayBegin(cal.getTime());
    }

    /**
     * 根据日期所在的年份，得到当年的结束日期，为来年的1月1日零点整.
     * 
     * @param date
     *            需要转换的日期
     * @return 来年的1月1日零点整
     */
    public static Date getYearEnd(Date date) {
        Calendar cal = getCalendar(date);
        int lastday = cal.getActualMaximum(GregorianCalendar.DAY_OF_YEAR);
        cal.set(Calendar.DAY_OF_YEAR, lastday);
        return getDayEnd(cal.getTime());
    }

    /**
     * 得到指定日期在当前星期中的天数.
     * 
     * <pre>
     * 例如2004-5-20日，返回5，
     * 每周以周日为开始按1计算，所以星期四为5
     * </pre>
     * 
     * @param date
     *            指定的日期
     * @return 返回天数
     */
    public static int getDayOfWeek(Date date) {
        return getCalendar(date).get(Calendar.DAY_OF_WEEK) - 1;
    }

    /**
     * 得到制定日期在当前月中的天数.
     * 
     * <pre>
     * 例如2004-5-20日，返回20.
     * </pre>
     * 
     * @param date
     *            指定的日期
     * @return 返回天数
     */
    public static int getDayOfMonth(Date date) {
        return getCalendar(date).get(Calendar.DAY_OF_MONTH);
    }

    /**
     * 得到制定日期在当前天数，例如2004-5-20日，返回141.
     * 
     * @param date
     *            指定的日期
     * @return 返回的天数
     */
    public static int getDayOfYear(Date date) {
        return getCalendar(date).get(Calendar.DAY_OF_YEAR);
    }

    /**
     * 得到指定日期在当前周内是第几天 (周一开始).
     * 
     * @param date
     *            指定日期
     * @return 周内天书
     */
    public static int getWeek(Date date) {
        return ((getCalendar(date).get(Calendar.DAY_OF_WEEK) - 1) + 7) % 7;
    }

    /**
     * 得到指定日期在当在一年中的月份数，从1开始.
     * 
     * @param date
     *            指定的日期
     * @return 指定日期在当在一年中的月份数
     */
    public static int getMonth(Date date) {
        return getCalendar(date).get(Calendar.MONTH) + 1;
    }

    /**
     * 得到指定日期的年份.
     * 
     * @param date
     *            指定的日期
     * @return 日期的年份
     */
    public static int getYear(Date date) {
        return getCalendar(date).get(Calendar.YEAR);
    }

    /**
     * 计算两个时间之间的时间差.
     * 
     * @param from
     *            开始
     * @param to
     *            结束
     * @return 时间差
     */
    public static long calculateTimeInMillis(Date from, Date to) {
        Calendar fromCal = getCalendar(from);
        Calendar toCal = getCalendar(to);
        if (fromCal.after(toCal)) {
            fromCal.setTime(to);
            toCal.setTime(from);
        }
        return toCal.getTimeInMillis() - fromCal.getTimeInMillis();
    }

    /**
     * 获取Calendar实例.
     * 
     * @param date
     *            日期类型
     */
    public static Calendar getCalendar(Date date) {
        Calendar gc = GregorianCalendar.getInstance();
        Date dateclone = (Date)date.clone();
        gc.setTime(dateclone);
        return gc;
    }

    /**
     * 根据 yyyy-MM-dd HH:mm:ss 日期格式，转换成数据库使用的时间戳格式.
     * 
     * @param strTimestamp
     *            以 yyyy-MM-dd HH:mm:ss 格式的时间字符串
     * @return 转换后的时间戳
     */
    public static java.sql.Timestamp parseTimestamp(String strTimestamp) {
        return new java.sql.Timestamp(DateBase.parseDate(strTimestamp,
                        DATE_PATTERN_DATETIME).getTime());
    }
}
